#!/bin/sh
#
# Copyright (c) 2007-2008 The PureDarwin Project.
# All rights reserved.
#
# @LICENSE_HEADER_START@
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# @LICENSE_HEADER_END@
#

#
# aladin <aladin@puredarwin.org>
#

#
# Set up a DOT file representing all dependencies of a port form MacPorts
#
# 	Usage: pd_portviz portname [+/-variant ...]
#

#
# Changelog
#
# 20081209 - License header update - aladin 
# 20081014 - Minor fixes - aladin 
# 20081012 - Process minimized (as the result graphs) 
#            Bugs, cosmetic fixed - aladin
# 20081010 - Variants added - aladin
# 20081008 - Initial release - aladin
#

i=0

function fancy_wait {
	if [ $i -eq 0 ]; then
		echo "-"
	fi

	if [ $i = 1 ]; then
		echo "\\"
	fi

	if [ $i = 2 ]; then
		echo "|"
	fi

	if [ $i = 4 ]; then
		echo "/"
	fi

	echo ""
	i=$(expr $i + 1)
}

#fancy_wait

# Initialization
echo $1 | grep '^[0-9]$' > /dev/null
if [ $? -eq 0 ]; then
	# Recursive level count found in arg1
	LEVEL=$1
	# Eating arg1
	shift
else
	#
	# Preventive tests
	#

	# Ensure root exclusivity
	if [ "$UID" -ne 0 ]; then
		echo "$(tput bold)Aborting!$(tput reset) You must be root in order to run \`$(basename $0)'"
		exit 1
	fi

	# Ensure portname is given
	if [ "$1" == "" ]; then
		echo "Usage: $(basename $0) portname [+/-variant ...]"
		exit
	fi

	# Ensure MacPorts in default/recommanded PATH
	if [ ! -x /opt/local/bin/port ]; then
		echo "$(tput bold)Aborting!$(tput reset) /opt/local/bin/port is not executable"
		echo "You must install the MacPorts project (http://www.macports.org/) in order to use \`$(basename $0)'"
		exit 1
	fi

	# Ensure portname existence
	PORT_INFO=`/opt/local/bin/port info $1 2>&1 | grep "Error: Port $1 not found"`
	if [ $? -eq 0 ]; then
		echo "$(tput bold)Aborting!$(tput reset) $PORT_INFO"
		exit 1
	fi

	# Ensure graphviz is available
	if [ ! -x /opt/local/bin/dot ]; then
		echo "$(tput bold)Warning!$(tput reset) /opt/local/bin/dot is not executable"
		echo "You should install \`graphviz' port for graphic output"
	fi

	# Global var sux
	export OUTPUT_FILENAME="$1.dot" 
	
	# Too lazy to loop
	laziness() {
		# Eating portname ($1)
		shift
		export VARIANTS="$*"
	}
	laziness $*

	echo
	echo "Generate dependencies graphs of $*"
	echo

	# DOT begin
	echo "digraph untitled {" > "$OUTPUT_FILENAME"

	# Do not draw the legend by default
	IS_LEGEND="false"
	# DOT Legend
	if [ "$IS_LEGEND" == "true" ]; then
	cat >>"$OUTPUT_FILENAME"<<EOOF
  	subgraph clusterLegend { 
 		<Root>          [shape=circle,       style=filled, fillcolor="#FFFFFFCF"];
        	<Runtime>       [shape=circle,       style=filled, fillcolor="#CCCCFFCF"];
  		<Library>       [shape=circle,       style=filled, fillcolor="#9999FFCF"];
      		<Build>         [shape=circle,       style=filled, fillcolor="#6666FFCF"]; 
		<Not here>      [shape=circle,       style=filled, fillcolor="#999999CF"];
      		<No dependency> [shape=doublecircle, style=filled, fillcolor="#6666FFCF"]; 
		<Library> -> <Library> [label=" blocker ",headclip=false, fontcolor=red,color=red,style=bold];
		subgraph cluster_$T_VARIANT {
			color=grey;
			fillcolor="lightgrey";
			style="rounded,filled";
			<Runtime> -> <Runtime> [label=" variant ",side="r", fontcolor=honeydew4,color=honeydew4,style=bold];
			<Build> -> <Build>     [label=" variant ",side="r", fontcolor=honeydew4,color=honeydew4,style=bold];
  		} 
		<Root> -> <Library>; 
		<Root> -> <Runtime>; 
		<Root> -> <Build>; 
		<Root> -> <Not here>; 
		<Root> -> <No dependency>; 
   		label="Legend of port dependencies." 
  	}  
EOOF
	fi

	# CLI legend
	echo " Legend:"
	echo " 1\t + foo (variant_x1, ..., variant_xn)"
	echo " 2\t |\`. bar (variant_y1, ..., variant_yn)               No dependency found"
	echo " 2\t |\`+ baz $(tput bold)impure!$(tput reset) /Blocker_1 -> file_involved_1       Impurity detected"
	echo " 2\t |\`+ baz $(tput bold)impure!$(tput reset) /Blocker_. -> file_involved_.       Impurity detected"
	echo " 2\t |\`+ baz $(tput bold)impure!$(tput reset) /Blocker_n -> file_involved_n       Impurity detected"
	echo " 2\t |\`+ baz (variant_z1, ..., variant_zn)               Has Dependenc{y|ies}"
	echo " 3\t | |\`- bar ()                                        Already processed"

	echo ""
	echo "Now generating dependencies tree and checking for \`\`purity'', please wait..."
	echo ""

	echo "	<$1> [style=filled, fillcolor=\"#FFFFFFCF\"] " >> "$OUTPUT_FILENAME"

	# Call himself

	$0 1 $1

	# DOT end
	echo "} " >> "$OUTPUT_FILENAME"

	echo
	echo "Generation of $OUTPUT_FILENAME $(tput bold)complete$(tput reset)."
	echo	
	echo "Now drawing graphs from $OUTPUT_FILENAME, please wait..."
	
	OUTPUT_FORMAT="png"

	/opt/local/bin/dot   -T$OUTPUT_FORMAT -o$1_directed.$OUTPUT_FORMAT      "$OUTPUT_FILENAME"
	echo "Generation of $1_directed.$OUTPUT_FORMAT $(tput bold)complete$(tput reset)."

	/opt/local/bin/circo -T$OUTPUT_FORMAT -o$1_circular.$OUTPUT_FORMAT      "$OUTPUT_FILENAME"
	echo "Generation of $1_circular.$OUTPUT_FORMAT $(tput bold)complete$(tput reset)."
	
	/opt/local/bin/twopi -T$OUTPUT_FORMAT -o$1_radial.$OUTPUT_FORMAT        "$OUTPUT_FILENAME"
	echo "Generation of $1_radial.$OUTPUT_FORMAT $(tput bold)complete$(tput reset)."
	
	/opt/local/bin/neato -T$OUTPUT_FORMAT -o$1_undirected.$OUTPUT_FORMAT    "$OUTPUT_FILENAME"
	echo "Generation of $1_undirected.$OUTPUT_FORMAT $(tput bold)complete$(tput reset)."
	
	/opt/local/bin/fdp   -T$OUTPUT_FORMAT -o$1_undirectedBIS.$OUTPUT_FORMAT "$OUTPUT_FILENAME"
	echo "Generation of $1_undirectedBIS.$OUTPUT_FORMAT $(tput bold)complete$(tput reset)."
	
	exit 0
fi

#
# Variables
#

# Default port path
PORT=/opt/local/bin/port


PORT_VARIANTS=""
PORT_INFO=""
PORT_INSTALLED=""
PORT_ACTIVATE=""
PORT_CONTENTS=""
PORT_OBJECT_FILE_SHARED_LIBS=""
PORT_BLOCKER_COUNT=0

# Simple blacklist based on https://sites.google.com/a/puredarwin.org/puredarwin/developers/macports/purity
PORT_BLOCKERS="
/AppKit.framework
/ApplicationServices.framework
/Carbon.framework
/Cocoa.framework
/CoreServices.framework
/Foundation.framework
/Symbolication.framework
"

# Cosmectic padding
TEXT_PADDING=""
for ((x=1;  x <= $[$LEVEL - 1] ; x++))  
do
 	 TEXT_PADDING="$TEXT_PADDING |"
done

if [ $LEVEL -gt 1 ]; then
	#TEXT_PADDING=" $LEVEL\t$TEXT_PADDING\`+"
	TEXT_PADDING=" \t$TEXT_PADDING\`+"
else	
	#TEXT_PADDING=" $LEVEL\t$TEXT_PADDING +"
	TEXT_PADDING=" \t$TEXT_PADDING +"
fi
# portname already found and processed?
#
#	The dot file is used as a simple cache
grep "<$1> \[shape = .*circle\];" "$OUTPUT_FILENAME" > /dev/null
if [ ! $? -eq 0 ]; then
	# port installed portname?
	PORT_INSTALLED=`$PORT installed $1 | grep "$1 \@"`
	if [ $? -eq 0 ]; then
		# is the portname active?
		PORT_ACTIVATE=`echo $PORT_INSTALLED | grep '(active)'`
		if [ $? -eq 0 ]; then
			# port contents portname ?
			PORT_CONTENTS=`$PORT contents $1 | grep -v "Port $1 contains"`
			if [ $? -eq 0 ]; then
				# inspecting all the mach-o files of the portname
				for PORT_OBJECT_FILE in $PORT_CONTENTS; do
					# otool map the object file?
					PORT_OBJECT_FILE_SHARED_LIBS=`/usr/bin/otool -L $PORT_OBJECT_FILE 2> /dev/null`
					if [ $? -eq 0 ]; then
						# is the file an object file? (Because otool return 0 even if it is or if it is not an object file)
						echo $PORT_OBJECT_FILE_SHARED_LIBS | grep "is not an object file" > /dev/null
						if [ ! $? -eq 0 ]; then
							# looking for current blocker(s)
							for PORT_BLOCKER in $PORT_BLOCKERS; do
								echo $PORT_OBJECT_FILE_SHARED_LIBS | grep "$PORT_BLOCKER" > /dev/null
								if [ $? -eq 0 ]; then
									echo "$TEXT_PADDING $1 $(tput bold)impure!$(tput reset) $PORT_BLOCKER -> $PORT_OBJECT_FILE "
									PORT_BLOCKER_COUNT=$[$PORT_BLOCKER_COUNT + 1]
								fi
							done
						fi
					fi
				done
				if [ $PORT_BLOCKER_COUNT -gt 0 ]; then
					echo "	<$1> -> <$1> [headclip=false, label=\" x$PORT_BLOCKER_COUNT \",fontcolor=red,color=red];" >> "$OUTPUT_FILENAME"
				fi
			# port contents portname is not available	
			else
				echo "	<$1> [fillcolor=\"#999999\"];" >> "$OUTPUT_FILENAME"
			fi
		# port is not activate
		else
			echo "	<$1> [fillcolor=\"#888888\"];" >> "$OUTPUT_FILENAME"
		fi
	# port is not installed
	else
		echo "	<$1> [fillcolor=\"#777777\"];" >> "$OUTPUT_FILENAME"
	fi

	# port deps portname?
	#
	# 	port info portname is used because port deps doesn't reflect consequences of variants added
	PORT_INFO==`$PORT info $1 $VARIANTS | sed "s/,//g" | egrep "Variants: |Build Dependencies|Library Dependencies|Runtime Dependencies" `
	if [ $? -eq 0 ]; then
		RUNTIME_DEPENDENCIES=`echo $PORT_INFO | grep "Runtime Dependencies"             | awk -F "Runtime Dependencies: " {'print $2'}`
		if [ "$RUNTIME_DEPENDENCIES" == "" ]; then
			LIBRARY_DEPENDENCIES=`echo $PORT_INFO | grep "Library Dependencies"     | awk -F "Library Dependencies: " {'print $2'}`
			if [ "$LIBRARY_DEPENDENCIES" == "" ]; then
				BUILD_DEPENDENCIES=`echo $PORT_INFO | grep "Build Dependencies" | awk -F "Build Dependencies: "   {'print $2'}`
			else
				BUILD_DEPENDENCIES=`echo $PORT_INFO | grep "Build Dependencies" | awk -F "Build Dependencies: "   {'print $2'} | awk -F "Library Dependencies: " {'print $1'}`
			fi
		else
			LIBRARY_DEPENDENCIES=`echo $PORT_INFO | grep "Library Dependencies"     | awk -F "Library Dependencies: " {'print $2'} | awk -F "Runtime Dependencies: " {'print $1'}`
			if [ "$LIBRARY_DEPENDENCIES" == "" ]; then
				BUILD_DEPENDENCIES=`echo $PORT_INFO | grep "Build Dependencies" | awk -F "Build Dependencies: "   {'print $2'} | awk -F "Runtime Dependencies: " {'print $1'}`
			else
				BUILD_DEPENDENCIES=`echo $PORT_INFO | grep "Build Dependencies" | awk -F "Build Dependencies: "   {'print $2'} | awk -F "Library Dependencies: " {'print $1'}`
			fi
		fi
	fi
	# port variants portname?
	#
	#	port info portname is also used in this case (no need to recall port command so)
	PORT_VARIANTS=`$PORT info $1 $VARIANTS  | grep 'Variants: '`
	if [ $? -eq 0 ]; then
		PORT_VARIANTS=`echo $PORT_VARIANTS | awk -F "Variants: " {'print $2'}   | awk -F ")" {'print $1'}`
		# inspecting all the submitted variant against portname variants
		for v in $VARIANTS; do
			T_VARIANT=`echo $v | sed "s/\+//g" | sed "s/\-//g"` > /dev/null
			# are variants matching?
			echo $PORT_VARIANTS | grep $T_VARIANT > /dev/null
			if [ $? -eq 0 ]; then
				PORT_VARIANTS=`echo $PORT_VARIANTS | sed "s/[( ]$T_VARIANT/$(tput bold)&$(tput reset)/g"`
				echo "	subgraph cluster_$T_VARIANT {" 	   >> "$OUTPUT_FILENAME"
				echo "		color=grey;"		   >> "$OUTPUT_FILENAME"
				echo "		fillcolor=\"lightgrey\";"  >> "$OUTPUT_FILENAME"
				echo "		style=\"rounded,filled\";" >> "$OUTPUT_FILENAME"
				#grep "<$1> -> <$1> \[label=\" $v \",fontcolor=honeydew4,color=honeydew4,style=bold\];" $OUTPUT_FILENAME  > /dev/null
				#if [ ! $? -eq 0 ]; then
					echo "		<$1> -> <$1> [label=\" $v \",fontcolor=honeydew4,color=honeydew4,style=bold];" >> "$OUTPUT_FILENAME"
				#fi	
				echo "	}" >> "$OUTPUT_FILENAME"
			fi
		done
	fi

	# End of recursion if no dependency found
	if [ "$BUILD_DEPENDENCIES$LIBRARY_DEPENDENCIES$RUNTIME_DEPENDENCIES" = "" ]; then
		echo "	<$1> [shape = doublecircle];" >> "$OUTPUT_FILENAME" ;
		echo "$TEXT_PADDING\b. $1 ($PORT_VARIANTS)"
	# Fall recursively into dependencies	
	else
		echo "$TEXT_PADDING $1 ($PORT_VARIANTS)"
		echo "	<$1> [shape = circle];" >> "$OUTPUT_FILENAME" ;

		# Iterative look in the build dependencies
		for dep in $BUILD_DEPENDENCIES; do
			# Again, using the dot file to avoid wasting time redoing things already done
			grep "	<$1> -> <$dep>;" "$OUTPUT_FILENAME" > /dev/null
			if [ ! $? -eq 0 ]; then
				echo "	<$dep> [style=filled, fillcolor=\"#6666FFCF\"];" >> "$OUTPUT_FILENAME"
				echo "	<$1> -> <$dep>;" >> "$OUTPUT_FILENAME"
				
				# Recursive call on each build dep
				$0 $[$LEVEL + 1] $dep
			fi
		done

		# Iterative look in the lib dependencies
		for dep in $LIBRARY_DEPENDENCIES; do
			# Again, using the dot file to avoid wasting time redoing things already done
			grep "	<$1> -> <$dep>;" "$OUTPUT_FILENAME" > /dev/null
			if [ ! $? -eq 0 ]; then
				echo "	<$dep> [style=filled, fillcolor=\"#9999FFCF\"];" >> "$OUTPUT_FILENAME"
				echo "	<$1> -> <$dep>;" >> "$OUTPUT_FILENAME"

				# Recursive call on each lib dependency
				$0 $[$LEVEL + 1] $dep
			fi
		done

		# Iterative look in the runtime dependencies
		for dep in $RUNTIME_DEPENDENCIES; do
			# Again, using the dot file to avoid wasting time redoing things already done
			grep "	<$1> -> <$dep>;" "$OUTPUT_FILENAME" > /dev/null
			if [ ! $? -eq 0 ]; then
				echo "	<$dep> [style=filled, fillcolor=\"#CCCCFFCF\"];" >> "$OUTPUT_FILENAME"
				echo "	<$1> -> <$dep>;" >> "$OUTPUT_FILENAME"

				# Recursive call on each run time dependency
				$0 $[$LEVEL + 1] $dep
			fi
		done
	fi
	
# port has already been processed
else
	echo "$TEXT_PADDING\b- $1 ($PORT_VARIANTS)"
fi
